
/*
 * Copyright (C) 2008-2009 Archie L. Cobbs. All rights reserved.
 *
 * $Id: ItemCache.java 310 2010-02-05 21:20:06Z archie.cobbs $
 */

package org.dellroad.sidekar;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;

/**
 * Caches canonical {@link Item} instances of a specific {@link Entity} using soft references.
 */
class ItemCache<T> {

    private final HashMap<Long, WeakItemReference<T>> map = new HashMap<Long, WeakItemReference<T>>();
    private final ReferenceQueue<T> queue = new ReferenceQueue<T>();
    private final Entity<T> entity;

    public ItemCache(Entity<T> entity) {
        this.entity = entity;
    }

    /**
     * Get the item with the given id, creating it if necessary.
     */
    synchronized T get(long id) {

        // Flush cleared references
        flush();

        // Sanity check
        assert id != 0;

        // Look for item already in cache
        WeakItemReference<T> itemRef = this.map.get(id);
        if (itemRef != null) {
            T item = itemRef.get();
            if (item != null)
                return item;
            this.map.remove(id);
        }

        // Create new Java instance
        T item = this.entity.instantiate(id);

        // Add it to the cache and return it
        this.map.put(id, new WeakItemReference<T>(item, this.queue));
        return item;
    }

    /**
     * Reap any cleared references.
     */
    synchronized void flush() {
        WeakItemReference<?> itemRef;
        while ((itemRef = (WeakItemReference<?>)this.queue.poll()) != null) {

            // Guard against the case where we polled an itemRef that has been replaced with another
            if (this.map.get(itemRef.id) != itemRef)
                continue;

            // Remove cleared reference
            this.map.remove(itemRef.id);
        }
    }

    // These are the values in the hash table. They weakly refer to the items associated with the given id.
    static final class WeakItemReference<R> extends WeakReference<R> {

        final long id;

        WeakItemReference(R item, ReferenceQueue<R> queue) {
            super(item, queue);
            this.id = ((Item<?>)item).getSidekarId();
        }
    }
}

